#ifndef _DIRENT_H
#include <dirent.h>
#endif

#ifndef _DLFCN_H
#include <dlfcn.h>
#endif

#ifndef _ERRNO_H
#include <errno.h>
#endif

#ifndef _NTH_LIB_H
#include "nth-lib.h"
#endif

#ifndef _REGEX_H
#include <regex.h>
#endif

#ifndef _STDLIB_H
#include <stdlib.h>
#endif

#ifndef _STRING_H
#include <string.h>
#endif

#ifndef _SYS_STAT_H
#include <sys/stat.h>
#endif

int Export_Nth_Directory(
	char * nth_path
){
	mkdir(
		nth_path,
		0755
	);

	return 0;
}

int Link_Nth_Plugins(struct Nth_Program * nth_program){

	int error = 0;

	regex_t plugin_pattern;

	char * possible_path_array[] = {
		0, // PWD
		"/usr/local/lib/nth",
		"/usr/local/lib",
		"/usr/lib/nth",
		"/usr/lib"
	};

	size_t possible_path_count = 0;
	size_t path_index = 0;

	if (!nth_program){
		return NTH_ERROR_NULL_POINTER;
	}

	possible_path_count = 0;
	possible_path_count += sizeof(possible_path_array);
	possible_path_count /= sizeof(char *);

	possible_path_array[0] = getenv("PWD");

	error = regcomp(
		&plugin_pattern,
		"nth-plugin--",
		0
	);

	if (error){

		Push_Nth_Failure(
			nth_program,
			"Link_Nth_Plugin",
			"Plugin search pattern failed to compile"
		);

		return NTH_ERROR_UNSPECIFIED;
	}

	while (path_index < possible_path_count){

		DIR * search_directory = 0;

		if (!possible_path_array[path_index]){
			path_index++;
			continue;
		}

		search_directory = opendir(
			possible_path_array[path_index]
		);

		if (!search_directory){
			path_index++;
			continue;
		}

		for (;;){

			char * full_library_path = 0;

			struct dirent * entry = 0;

			struct Nth_Plugin plugin;

			int (*initialize_plugin)(struct Nth_Plugin * nth_plugin) = 0;

			entry = readdir(search_directory);

			if (!entry){
				break;
			}

			error = regexec(
				&plugin_pattern,
				entry->d_name,
				0,
				0,
				0
			);

			if (error){
				continue;
			}

			error = Create_Nth_Path(
				&full_library_path
			);

			if (error){
				continue;
			}

			error = Set_Nth_Path_Prefix(
				&full_library_path,
				possible_path_array[path_index]
			);

			if (error){
				Destroy_Nth_Path(&full_library_path);
				continue;
			}

			error = Set_Nth_Path_Suffix(
				&full_library_path,
				entry->d_name
			);

			if (error){
				Destroy_Nth_Path(&full_library_path);
				continue;
			}

			memset(&plugin, 0, sizeof(plugin));

			plugin.library_handle = dlopen(
				full_library_path,
				RTLD_LAZY
			);

			Destroy_Nth_Path(&full_library_path);

			if (!plugin.library_handle){
				continue;
			}

			initialize_plugin = dlsym(
				plugin.library_handle,
				"Initialize_Nth_Plugin"
			);

			if (!initialize_plugin){
				dlclose(plugin.library_handle);
				continue;
			}

			error = initialize_plugin(
				&plugin
			);

			if (error){
				dlclose(plugin.library_handle);
				continue;
			}

			error = Push_Nth_Plugin(
				nth_program,
				&plugin
			);

			if (error){

				Push_Nth_Failure(
					nth_program,
					"Link_Nth_Plugins",
					"Failed to push plugin into program structure"
				);

				closedir(search_directory);
				regfree(&plugin_pattern);

				Unlink_Nth_Plugins(nth_program);

				return NTH_ERROR_UNSPECIFIED;
			}
		}

		closedir(search_directory);

		path_index++;
	}

	regfree(&plugin_pattern);

	return 0;
}

int Unlink_Nth_Plugins(
	struct Nth_Program * nth_program
){
	size_t plugin_index = 0;
	size_t plugin_count = 0;

	struct Nth_Plugin * plugin_array = 0;

	int (*clean_plugin)(struct Nth_Plugin * plugin);

	if (!nth_program){
		return NTH_ERROR_NULL_POINTER;
	}

	plugin_index = 0;
	plugin_count = nth_program->plugin_count;
	plugin_array = nth_program->plugin_array;

	while (plugin_index < plugin_count){

		clean_plugin = dlsym(
			plugin_array[plugin_index].library_handle,
			"Clean_Nth_Plugin"
		);

		if (clean_plugin){
			clean_plugin(&plugin_array[plugin_index]);
		}

		dlclose(plugin_array[plugin_index].library_handle);
		plugin_index++;
	}

	free(nth_program->plugin_array);

	nth_program->plugin_array = 0;
	nth_program->plugin_count = 0;

	return 0;
}

